/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tuplestoremasterserver;

import core.AbstractCoreClient;
import core.AbstractCoreServer;
import core.BestFit;
import core.Callback;
import core.IServerCallBack;
import core.ProtocolSD;
import core.ProtocolSD.IProtocolListener;
import core.ServerCallBack;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import logging.LOG;
import protocol.TSServerInfo;
import protocol.TSTicket;

/**
 *
 * @author Jorge
 */
public class MasterServer {

    private static int MASTER_CLIENT_PORT = 3000;
    private static int MASTER_SERVER_PORT = 3500;
    private static int ticketGenerator = 1;
    private static int serverGenerator = 1;

    private static Map<Integer, TSServerInfo> servers = new HashMap<Integer, TSServerInfo>();
    private static List<TSServerInfo> connected = new LinkedList<TSServerInfo>();

    private static Map<Integer, TSServerInfo> keyMapping = new HashMap<Integer, TSServerInfo>();

    private static Map<Integer, TSTicket> tickets = new HashMap<Integer, TSTicket>();

    private static BestFit bestFit = new BestFit();

    private static MasterServer server = new MasterServer();

    public static synchronized int getTicket() {
        return ticketGenerator++;
    }

    public static synchronized int getServer() {
        return serverGenerator++;
    }

    public static MasterServer getInstance() {
        return server;
    }

    public static void updateKeys(Map<Integer,Integer> map){
        synchronized(keyMapping){
           for(Map.Entry<Integer,Integer> entry : map.entrySet()){
            keyMapping.put(entry.getKey(), getServer(entry.getValue()));
           }
        }
    }

    public static void commitKey(final Integer get, final TSServerInfo server,final int ticket, final Callback<Integer, byte[]> callBack) {
        ServerCallBack<Integer, byte[]> serverCallBack = new ServerCallBack<Integer, byte[]>(callBack) {

            public void onFinishClient(ProtocolSD protocolSD1) {
                // Notifica Remoção
                LOG.append("Envio de remoção");
                protocolSD1.getKey().interestOps(SelectionKey.OP_WRITE);
                protocolSD1.write(new byte[]{(byte) ProtocolSD.Operation.REMOVE.ordinal(), 1});
                protocolSD1.writeInt(4);
                protocolSD1.writeInt(get);

            }

            @Override
            public void onWriteComplete(ProtocolSD protocolSD1) {
                //Espera por resposta
                protocolSD1.getKey().interestOps(SelectionKey.OP_READ);
            }

            @Override
            public void onReadArgument(byte[] data, ProtocolSD protocolSD) {
                if(data[4]==ProtocolSD.SUCCESS){
                    synchronized(tickets){
                        if(tickets.containsKey(get)){
                        if(tickets.get(get).getTicket() > ticket){
                            ((ServerCallBack) this).onNewResult(ProtocolSD.byteArrayToInt(data, 0), new byte[]{ProtocolSD.ERROR});
                            return;
                        }
                    }
                    }
                        MasterServer.commit(get, ticket, server);
                       ((ServerCallBack) this).onNewResult(ProtocolSD.byteArrayToInt(data, 0),new byte[]{ ProtocolSD.SUCCESS});
                    }
            else{
                ((ServerCallBack) this).onNewResult(ProtocolSD.byteArrayToInt(data, 0),new byte[]{ ProtocolSD.ERROR});
            }
            }

            @Override
            public void onReadAllArguments(ProtocolSD protocolSD) {
                if(protocolSD==null){
                   ((ServerCallBack)this).onNewResult(0, null);
                }
                ((ServerCallBack) this).onFinish();
                LOG.append("Commit da chave");
            }
        };
        
        TSServerInfo server1 = MasterServer.getServerOfKey(get);

        if (server1 == null) {
            byte [] bs = new byte[5];
            byte [] aux = ProtocolSD.intToByte(get);
            for(int i=0;i<4;i++){
                bs[i]= aux[i];
            }

            bs[4]= ProtocolSD.SUCCESS;

            serverCallBack.onReadArgument(bs,null);
            serverCallBack.onReadAllArguments(null);

        } else {
            MasterServer.getInstance().connect(server1.getServerAddress(), server1.getComunicationPort(), serverCallBack);
        }

    }

    private static void commit(Integer get, int ticket, TSServerInfo server) {
        synchronized(keyMapping){
            keyMapping.put(get, server);
        }
        synchronized(tickets){
            tickets.put(get, new TSTicket(server, ticket));
        }
    }

    private MasterServer() {
        initClientServer(MASTER_CLIENT_PORT);
        initServerServer(MASTER_SERVER_PORT);
        initServerClient();
    }

    public static int addServer(TSServerInfo server) {
       
            server.setUniqueID(getServer());
            synchronized (servers) {
                servers.put(server.getUniqueID(), server);
                connected.add(server);
            }
            return server.getUniqueID();
    }

    public static boolean  updateServer(int key, long avalilableSize , long keySize ,long usedSize) {
        if (servers.containsKey(server)) {
            TSServerInfo info = servers.get(key);
            info.setAvailableSize(avalilableSize);
            info.setKeySize(keySize);
            info.setUsedSize(usedSize);
            return true;
        }
        return false;
    }

    public static void removeServer(TSServerInfo server) {
        synchronized (servers) {
            servers.remove(server.getUniqueID());
        }
    }

    public static void putKey(Integer key, TSServerInfo server) {
        synchronized (keyMapping) {
            keyMapping.put(key, server);
        }
    }

    public static TSServerInfo getServer(Integer key) {
        return servers.get(key);
    }

    public static TSServerInfo getServerOfKey(Integer key) {
        return keyMapping.get(key);
    }

    public static TSServerInfo getBestFit(Integer key, Integer size) {
        return bestFit.bestfit(ticketGenerator, connected);
    }
    private AbstractCoreServer clientServer;
    private AbstractCoreServer serverServer;
    private AbstractCoreClient serverClient;

    public void initClientServer(int port) {
        clientServer = new AbstractCoreServer() {

            @Override
            public void handlerServerError(Throwable exception) {
                LOG.append("Erro de servidor " + exception.getMessage());
            }

            @Override
            public void handlerFatalErrorCliente(Throwable throwable) {
                LOG.append("Erro cliente " + throwable.getMessage());
            }

            @Override
            protected IProtocolListener createInstance() {
                return new ClientListener();
            }
        };
        clientServer.connect(port);
    }

    public void initServerServer(int port) {
        serverServer = new AbstractCoreServer() {

            @Override
            public void handlerServerError(Throwable exception) {
                LOG.append("Erro de servidor " + exception.getMessage());
            }

            @Override
            public void handlerFatalErrorCliente(Throwable throwable) {
                LOG.append("Erro cliente " + throwable.getMessage());
            }

            @Override
            protected IProtocolListener createInstance() {
                return new ServerServerListener();
            }
        };
        serverServer.connect(port);

    }

    public void initServerClient() {
        serverClient = new AbstractCoreClient() {

            @Override
            protected IProtocolListener createInstance() {
                return new ServerClientListener();
            }

            @Override
            public void handlerServerError(Throwable exception) {
                LOG.append("Erro de servidor " + exception.getMessage());
            }

            @Override
            public void handlerFatalErrorCliente(Throwable throwable) {
                LOG.append("Erro cliente " + throwable.getMessage());
            }

            @Override
            public void handleFinishConnect(SelectionKey key) {
                LOG.append("Novo Cliente Ligado");
            }
        };

        serverClient.connect();
    }

    public void connect(String host, int port, IServerCallBack callback) {
        serverClient.connect(host, port, callback);
    }

    public void notifyServer(String host, int port,final int key,final int ticket){
        serverClient.connect(host, port, new ServerCallBack(null) {

            public void onFinishClient(ProtocolSD protocolSD) {
                protocolSD.write(new byte[]{(byte)ProtocolSD.Operation.NOTIFY.ordinal(),(byte)1});
                protocolSD.writeInt(8);
                protocolSD.writeInt(key);
                protocolSD.writeInt(ticket);
            }

            @Override
            public void onWriteComplete(ProtocolSD protocolSD) {
                try{
                    protocolSD.close();
                }catch(IOException exception){
                      exception.printStackTrace();
                }
            }
        });
    }

    
}
